Completed
Push — master ( b5febe...9fa866 )
by Pieter
02:32
created

Gui.drawEvent   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 13
rs 9.4285
c 1
b 0
f 0
cc 1
nc 1
nop 0
1
const GitAmp = (function(exports, $) {
2
    'use strict';
3
4
    /**
5
     * AudioPlayer
6
     */
7
    const AudioPlayer = (function() {
8
        // something somewhere needs a global volume variable
9
        // not sure what thing it is, but adding this line works
10
        exports.volume = 0.6;
11
12
        const maxPitch = 100.0;
13
        const logUsed  = 1.0715307808111486871978099;
14
15
        const maximumSimultaneousNotes = 2;
16
        const soundLength = 300;
17
18
        function AudioPlayer() {
19
            this.currentlyPlayingSounds = 0;
20
21
            this.sounds = {
22
                celesta: this.initializeCelesta(),
23
                clav: this.initializeClav(),
24
                swells: this.initializeSwells()
25
            };
26
27
            //noinspection JSUnresolvedVariable
28
            exports.Howler.volume(0.7);
29
        }
30
31
        AudioPlayer.prototype.initializeCelesta = function() {
32
            const sounds = [];
33
34
            for (let i = 1; i <= 24; i++) {
35
                let filename = (i > 9) ? 'c0' + i : 'c00' + i;
36
37
                //noinspection JSUnresolvedFunction
38
                sounds.push(new Howl({
0 ignored issues
show
Bug introduced by
The variable Howl seems to be never declared. If this is a global, consider adding a /** global: Howl */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
39
                    src : [
40
                        'https://d1fz9d31zqor6x.cloudfront.net/sounds/celesta/' + filename + '.ogg',
41
                        'https://d1fz9d31zqor6x.cloudfront.net/sounds/celesta/' + filename + '.mp3'
42
                    ],
43
                    volume : 0.7,
44
                    buffer: true
45
                }));
46
            }
47
48
            return sounds;
49
        };
50
51
        AudioPlayer.prototype.initializeClav = function() {
52
            const sounds = [];
53
54
            for (let i = 1; i <= 24; i++) {
55
                let filename = (i > 9) ? 'c0' + i : 'c00' + i;
56
57
                //noinspection JSUnresolvedFunction
58
                sounds.push(new Howl({
0 ignored issues
show
Bug introduced by
The variable Howl seems to be never declared. If this is a global, consider adding a /** global: Howl */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
59
                    src : [
60
                        'https://d1fz9d31zqor6x.cloudfront.net/sounds/clav/' + filename + '.ogg',
61
                        'https://d1fz9d31zqor6x.cloudfront.net/sounds/clav/' + filename + '.mp3'
62
                    ],
63
                    volume : 0.7,
64
                    buffer: true
65
                }));
66
            }
67
68
            return sounds;
69
        };
70
71
        AudioPlayer.prototype.initializeSwells = function() {
72
            const sounds = [];
73
74
            for (let i = 1; i <= 3; i++) {
75
                //noinspection JSUnresolvedFunction
76
                sounds.push(new Howl({
0 ignored issues
show
Bug introduced by
The variable Howl seems to be never declared. If this is a global, consider adding a /** global: Howl */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
77
                    src : [
78
                        'https://d1fz9d31zqor6x.cloudfront.net/sounds/swells/swell' + i + '.ogg',
79
                        'https://d1fz9d31zqor6x.cloudfront.net/sounds/swells/swell' + i + '.mp3'
80
                    ],
81
                    volume : 0.7,
82
                    buffer: true
83
                }));
84
            }
85
86
            return sounds;
87
        };
88
89
        AudioPlayer.prototype.getSoundIndex = function(size, type) {
90
            const pitch = 100 - Math.min(maxPitch, Math.log(size + logUsed) / Math.log(logUsed));
91
            let index   = Math.floor(pitch / 100.0 * this.sounds[type].length);
92
93
            index += Math.floor(Math.random() * 4) - 2;
94
            index = Math.min(this.sounds[type].length - 1, index);
95
            index = Math.max(1, index);
96
97
            return index;
98
        };
99
100
        AudioPlayer.prototype.playSound = function(sound) {
101
            if (this.currentlyPlayingSounds >= maximumSimultaneousNotes) {
102
                return;
103
            }
104
105
            sound.play();
106
107
            this.currentlyPlayingSounds++;
108
109
            setTimeout(function() {
110
                this.currentlyPlayingSounds--;
111
            }.bind(this), soundLength);
112
        };
113
114
        AudioPlayer.prototype.playCelesta = function(size) {
115
            this.playSound(this.sounds.celesta[this.getSoundIndex(size, 'celesta')]);
116
        };
117
118
        AudioPlayer.prototype.playClav = function(size) {
119
            this.playSound(this.sounds.clav[this.getSoundIndex(size, 'clav')]);
120
        };
121
122
        AudioPlayer.prototype.playSwell = function() {
123
            this.playSound(this.sounds.swells[Math.round(Math.random() * (this.sounds.swells.length - 1))]);
124
        };
125
126
        return AudioPlayer;
127
    }());
128
129
    /**
130
     * Gui
131
     */
132
    const Gui = (function() {
133
        const scaleFactor = 6;
134
        const textColor   = '#ffffff';
135
        const maxLife     = 20000;
136
137
        function Gui() {
138
            //noinspection JSUnresolvedVariable
139
            this.svg = exports.d3.select('#area').append('svg');
140
141
            exports.addEventListener('resize', this.resize.bind(this));
142
143
            this.setupVolumeSlider();
144
            this.resize();
145
        }
146
147
        Gui.prototype.setupVolumeSlider = function() {
148
            //noinspection JSUnresolvedFunction
149
            $('#volumeSlider').slider({
150
                max: 100,
151
                min: 0,
152
                value: volume * 100,
0 ignored issues
show
Bug introduced by
The variable volume seems to be never declared. If this is a global, consider adding a /** global: volume */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
153
                slide: function (event, ui) {
154
                    //noinspection JSUnresolvedVariable
155
                    exports.Howler.volume(ui.value/100.0);
156
                },
157
                change: function (event, ui) {
158
                    //noinspection JSUnresolvedVariable
159
                    exports.Howler.volume(ui.value/100.0);
160
                }
161
            });
162
        };
163
164
        Gui.prototype.getWidth = function() {
165
            return exports.innerWidth;
166
        };
167
168
        Gui.prototype.getHeight = function() {
169
            return exports.innerHeight - $('header').height();
170
        };
171
172
        Gui.prototype.resize = function() {
173
            this.svg.attr('width', this.getWidth());
174
            this.svg.attr('height', this.getHeight());
175
        };
176
177
        Gui.prototype.drawEvent = function(event) {
178
            let opacity = 1 / (100 / event.getMessage().length);
179
180
            if (opacity > 0.5) {
181
                opacity = 0.5;
0 ignored issues
show
Unused Code introduced by
The assignment to variable opacity seems to be never used. Consider removing it.
Loading history...
182
            }
183
184
            let size = event.getMessage().length;
185
            let label_text;
186
            let ring_radius = 80;
187
            let ring_anim_duration = 3000;
188
189
            let edit_color = '#FFFFFF';
190
191
            switch(event.getType()){
0 ignored issues
show
Coding Style introduced by
As per coding-style, switch statements should have a default case.
Loading history...
192
                case "PushEvent":
193
                    label_text = event.getActorName() + " pushed to " + event.getRepositoryName();
194
                    edit_color = '#22B65D';
195
                    break;
196
                case "PullRequestEvent":
197
                    label_text = event.getActorName() + " " +
198
                        event.getAction() + " " + " a PR for " + event.getRepositoryName();
199
                    edit_color = '#8F19BB';
200
                    ring_anim_duration = 10000;
201
                    ring_radius = 600;
202
                    break;
203
                case "IssuesEvent":
204
                    label_text = event.getActorName() + " " +
205
                        event.getAction() + " an issue in " + event.getRepositoryName();
206
                    edit_color = '#ADD913';
207
                    break;
208
                case "IssueCommentEvent":
209
                    label_text = event.getActorName() + " commented in " + event.getRepositoryName();
210
                    edit_color = '#FF4901';
211
                    break;
212
                case "ForkEvent":
213
                    label_text = event.getActorName() + " forked " + event.getRepositoryName();
214
                    edit_color = '#0184FF';
215
                    break;
216
                case "CreateEvent":
217
                    label_text = event.getActorName() + " created " + event.getRepositoryName();
218
                    edit_color = '#00C0C0';
219
                    break;
220
                case "WatchEvent":
221
                    label_text = event.getActorName() + " watched " + event.getRepositoryName();
222
                    edit_color = '#E60062';
223
                    break;
224
            }
225
226
            let no_label = false;
0 ignored issues
show
Unused Code introduced by
The variable no_label seems to be never used. Consider removing it.
Loading history...
227
228
            size = Math.max(Math.sqrt(Math.abs(size)) * scaleFactor, 3);
229
230
            //noinspection JSUnresolvedFunction
231
            Math.seedrandom(event.getMessage());
232
            let x = Math.random() * (this.getWidth() - size) + size;
233
            let y = Math.random() * (this.getHeight() - size) + size;
234
235
            let circle_group = this.svg.append('g')
236
                .attr('transform', 'translate(' + x + ', ' + y + ')')
237
                .attr('fill', edit_color)
238
                .style('opacity', 1);
239
240
            let ring = circle_group.append('circle');
241
            ring.attr({r: size, stroke: 'none'});
242
            ring.transition()
243
                .attr('r', size + ring_radius)
244
                .style('opacity', 0)
245
                .ease(Math.sqrt)
246
                .duration(ring_anim_duration)
247
                .remove();
248
249
            let circle_container = circle_group.append('a');
250
            circle_container.attr('xlink:href', event.getUrl());
251
            circle_container.attr('target', '_blank');
252
            circle_container.attr('fill', textColor);
253
254
            let circle = circle_container.append('circle');
255
            circle.classed(event.getType(), true);
256
            circle.attr('r', size)
257
                .attr('fill', edit_color)
258
                .transition()
259
                .duration(maxLife)
260
                .style('opacity', 0)
261
                .remove();
262
263
            circle_container.on('mouseover', function() {
264
                circle_container.append('text')
265
                    .text(label_text)
0 ignored issues
show
Bug introduced by
The variable label_text seems to not be initialized for all possible execution paths. Are you sure text handles undefined variables?
Loading history...
266
                    .classed('label', true)
267
                    .attr('text-anchor', 'middle')
268
                    .attr('font-size', '0.8em')
269
                    .transition()
270
                    .delay(1000)
271
                    .style('opacity', 0)
272
                    .duration(2000)
273
                    .each(function() { no_label = true; })
0 ignored issues
show
Unused Code introduced by
The variable no_label seems to be never used. Consider removing it.
Loading history...
274
                    .remove();
275
            });
276
277
            let text = circle_container.append('text')
0 ignored issues
show
Unused Code introduced by
The assignment to variable text seems to be never used. Consider removing it.
Loading history...
278
                .text(label_text)
0 ignored issues
show
Bug introduced by
The variable label_text seems to not be initialized for all possible execution paths. Are you sure text handles undefined variables?
Loading history...
279
                .classed('article-label', true)
280
                .attr('text-anchor', 'middle')
281
                .attr('font-size', '0.8em')
282
                .transition()
283
                .delay(2000)
284
                .style('opacity', 0)
285
                .duration(5000)
286
                .each(function() { no_label = true; })
0 ignored issues
show
Unused Code introduced by
The variable no_label seems to be never used. Consider removing it.
Loading history...
287
                .remove();
288
289
            // Remove HTML of decayed events
290
            // Keep it less than 50
291
            let $area = $('#area');
292
            if($area.find('svg g').length > 50){
293
                $area.find('svg g:lt(10)').remove();
294
            }
295
        };
296
297
        return Gui;
298
    }());
299
300
    /**
301
     * ConnectedUsersMessage
302
     */
303
    function ConnectedUsersMessage(response) {
304
        //noinspection JSUnresolvedVariable
305
        this.count = response.connectedUsers;
306
    }
307
308
    ConnectedUsersMessage.prototype.getCount = function() {
309
        return this.count;
310
    };
311
312
    /**
313
     * EventMessage
314
     */
315
    function EventMessage(event) {
316
        this.event = event;
317
    }
318
319
    EventMessage.prototype.getId = function() {
320
        //noinspection JSUnresolvedVariable
321
        return this.event.id;
322
    };
323
324
    EventMessage.prototype.getType = function() {
325
        //noinspection JSUnresolvedVariable
326
        return this.event.type;
327
    };
328
329
    EventMessage.prototype.getAction = function() {
330
        //noinspection JSUnresolvedVariable
331
        return this.event.action;
332
    };
333
334
    EventMessage.prototype.getRepositoryName = function() {
335
        //noinspection JSUnresolvedVariable
336
        return this.event.repoName;
337
    };
338
339
    EventMessage.prototype.getActorName = function() {
340
        //noinspection JSUnresolvedVariable
341
        return this.event.actorName;
342
    };
343
344
    EventMessage.prototype.getUrl = function() {
345
        //noinspection JSUnresolvedVariable
346
        return this.event.eventUrl;
347
    };
348
349
    EventMessage.prototype.getMessage = function() {
350
        //noinspection JSUnresolvedVariable
351
        return this.event.message;
352
    };
353
354
    /**
355
     * EventMessageCollection
356
     */
357
    function EventMessageCollection(response) {
358
        this.events = [];
359
360
        for (let i = 0; i < response.length; i++) {
361
            this.events.push(new EventMessage(response[i]));
362
        }
363
    }
364
365
    EventMessageCollection.prototype.forEach = function(callback) {
366
        for (let i = 0; i < this.events.length; i++) {
367
            callback(this.events[i]);
368
        }
369
    };
370
371
    /**
372
     * EventMessagesFactory
373
     */
374
    function EventMessagesFactory () {
375
    }
376
377
    EventMessagesFactory.prototype.build = function(response) {
378
        const parsedResponse = JSON.parse(response.data);
379
380
        if (parsedResponse.hasOwnProperty('connectedUsers')) {
381
            return new ConnectedUsersMessage(parsedResponse);
382
        }
383
384
        return new EventMessageCollection(parsedResponse);
385
    };
386
387
    /**
388
     * EventQueue
389
     */
390
    function EventQueue() {
391
        this.queue = [];
392
    }
393
394
    EventQueue.prototype.append = function(eventMessages) {
395
        eventMessages.forEach(function(event) {
396
            if (this.exists(event)) {
397
                return;
398
            }
399
400
            this.queue.push(event);
401
        }.bind(this));
402
403
        if (this.queue.length > 1000) {
404
            this.queue = this.queue.slice(0, 1000);
405
        }
406
    };
407
408
    EventQueue.prototype.exists = function(event) {
409
        for (let i = 0; i < this.queue.length; i++) {
410
            if (event.getId() === this.queue[i].getId()) {
411
                return true;
412
            }
413
        }
414
415
        return false;
416
    };
417
418
    EventQueue.prototype.get = function() {
419
        return this.queue.shift();
420
    };
421
422
    EventQueue.prototype.count = function() {
423
        return this.queue.length;
424
    };
425
426
    /**
427
     * Connection
428
     */
429
    function Connection(eventMessageFactory) {
430
        this.eventMessageFactory = eventMessageFactory;
431
432
        this.connection = null;
433
        this.handlers   = [];
434
    }
435
436
    Connection.prototype.start = function() {
437
        let protocol = 'ws://';
438
439
        if (exports.location.protocol === "https:") {
440
            protocol = 'wss://';
441
        }
442
443
        this.connection = new WebSocket(protocol + exports.location.host + '/ws');
0 ignored issues
show
Bug introduced by
The variable WebSocket seems to be never declared. If this is a global, consider adding a /** global: WebSocket */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
444
445
        this.connection.addEventListener('message', this.handleMessage.bind(this));
446
        this.connection.addEventListener('open', this.handleOpen.bind(this));
447
        this.connection.addEventListener('close', this.handleClose.bind(this));
448
        this.connection.addEventListener('error', this.handleError.bind(this));
449
    };
450
451
    Connection.prototype.registerHandler = function(handler) {
452
        this.handlers.push(handler);
453
    };
454
455
    Connection.prototype.handleMessage = function(response) {
456
        const message = this.eventMessageFactory.build(response);
457
458
        for (let i = 0; i < this.handlers.length; i++) {
459
            this.handlers[i](message);
460
        }
461
    };
462
463
    Connection.prototype.handleOpen = function() {
464
        //document.getElementsByTagName('svg')[0].style.backgroundColor = svg_background_color_online;
465
        //document.getElementsByTagName('header')[0].style.backgroundColor = svg_background_color_online;
466
467
        const elements = document.querySelectorAll('.events-remaining-text, .events-remaining-value, .online-users-div');
468
469
        for (let i = 0; i < elements.length; i++) {
470
            elements[i].style.visibility = 'visible';
471
        }
472
    };
473
474
    Connection.prototype.handleClose = function() {
475
        //document.getElementsByTagName('svg')[0].style.backgroundColor = svg_background_color_offline;
476
        //document.getElementsByTagName('header')[0].style.backgroundColor = svg_background_color_offline;
477
478
        this.connection = null;
479
    };
480
481
    Connection.prototype.handleError = function() {
482
        this.handleClose();
483
484
        const reTryInterval = setInterval(function() {
485
            if (this.connection !== null) {
486
                clearInterval(reTryInterval);
487
488
                return;
489
            }
490
491
            this.start();
492
        }.bind(this), 5000);
493
    };
494
495
    /**
496
     * Application
497
     */
498
    function Application() {
499
        this.queue = new EventQueue();
500
        this.audio = new AudioPlayer();
501
        this.gui   = new Gui();
502
    }
503
504
    Application.prototype.run = function() {
505
        const connection = new Connection(new EventMessagesFactory());
506
507
        connection.registerHandler(this.process.bind(this));
508
509
        connection.start();
510
511
        this.loop();
512
    };
513
514
    Application.prototype.process = function(message) {
515
        if (message instanceof ConnectedUsersMessage) {
516
            document.getElementsByClassName('online-users-count')[0].textContent = message.getCount();
517
518
            return;
519
        }
520
521
        this.queue.append(message);
522
    };
523
524
    Application.prototype.loop = function() {
525
        setTimeout(function() {
526
            this.processEvent(this.queue.get());
527
528
            document.getElementsByClassName('events-remaining-value')[0].textContent = this.queue.count();
529
530
            this.loop();
531
        }.bind(this), Math.floor(Math.random() * 1000) + 500);
532
    };
533
534
    Application.prototype.processEvent = function(event) {
535
        if (!event.getMessage()) {
536
            return;
537
        }
538
539
        if (event.getType() === 'IssuesEvent' || event.getType() === 'IssueCommentEvent') {
540
            this.audio.playClav(event.getMessage().length * 1.1);
541
        } else if(event.getType() === 'PushEvent') {
542
            this.audio.playCelesta(event.getMessage().length * 1.1);
543
        }else{
544
            this.audio.playSwell();
545
        }
546
547
        this.gui.drawEvent(event);
548
    };
549
550
    return Application;
551
}(window, jQuery));
552
553
$(function() {
554
    new GitAmp().run();
555
});
556